#ifndef  NAIVE_BAYSE_H
#define  NAIVE_BAYSE_H
#include "attr_prob.h"
#include "classifier.h"
#include "instances.h"
#include "prob_instance.h"
#include "major_prob.h"
#include "nju_format.h"
#include <vector>
#include <algorithm>

//==========================================================
// Class: naive_bayes
//        This class is one of the concrete classifiers
//        based on the Bayes theorem.
//Author: Junix<unix_jun@yahoo.com.cn>
//Date:   2007-11-18
//==========================================================

class naive_bayes:public classifier
{
public:
//==========================================================
//  Constructor and Deconstructor
//==========================================================
    naive_bayes ()
    {
        _training_set = NULL;
        _test_set = NULL;
        _classify_set = NULL;
    }

    naive_bayes (instances * training_set)
    {
        _training_set = training_set;
    }

    naive_bayes (instances* training_set, instances* classify_set)
    {
        _training_set = training_set;
        _classify_set = classify_set;
    }

    naive_bayes (instances * training_set, instances * test_set, instances * classify_set)
    {
        _training_set = training_set;
        _test_set = test_set;
        _classify_set = classify_set;
    }

    ~naive_bayes ()
    {
        delete _training_set;
        delete _test_set;
        delete _classify_set;
    }

//=================================================
// main functions to do the data mining
//=================================================

    //this function stands for learning step
    //before this,you should set the training set.
    void learn ()
    {
        calculate_label_num ();
        calculate_training_coef ();
    }

    //another learn function
    void learn (instances* set)
    {
        set_training_set(set);
        learn ();
    }

    //this function stands for classify step
    //before this,you should set the classify set.
    void classify ()
    {
        predict_instances (*_classify_set);
    }

    //another classify function
    void classify (instances* set)
    {
        set_classify_set(set);
        classify ();
    }

    //test step
    void test()
    {
        float percent=test_instances(*_test_set);
	cout<<endl<<"========================================="<<endl;
        cout<<"Accuracy: "<<percent;
	cout<<endl<<"========================================="<<endl;
    }

    void test(instances* set)
    {
        set_test_set(set);
        test();
    }

//===============================================================
//   Use the coef. calculated to predict the instance
//   Classify new tuple or test the test set.
//===============================================================
    void predict_instances (instances & ines)
    {
        //fixing missing value should be done firstly.
        _classified_set.clear ();
        float prob;

        for (int i = 0; i < ines._instances->size (); i++)
        {
            prob = predict_instance (ines._instances->at (i));
            major_prob mp (ines._instances->at (i).major(), prob,ines._instances->at(i)._label.discrete());
            //cout<<ines._instances->at(i)._label.discrete()<<"  "<<prob<<endl;
            //prob_instance pins(prob,ines._instances->at(i));
            _classified_set.push_back (mp);
        }
    }

    //Ugly code for meeting the NJU special format requirement
    void nju_format()
    {
        vector<major_prob> _2G;
        vector<major_prob> _3G;
        remove_copy_if(_classified_set.begin(),_classified_set.end(),back_inserter(_2G),is_2G);
        remove_copy_if(_classified_set.begin(),_classified_set.end(),back_inserter(_3G),is_3G);
        _classified_set.clear();
        sort(_3G.begin(), _3G.end(),sort_by_prob_greater);
        sort(_2G.begin(), _2G.end(),sort_by_prob_less);
        copy(_2G.begin(), _2G.end(),back_inserter(_classified_set));
        copy(_3G.begin(), _3G.end(),back_inserter(_classified_set));
    }

    float test_instances (instances & ines)
    {
        float accuracy = 0;
        int total = 0;
        int correct = 0;

        for (int i = 0; i < ines._instances->size (); i++)
        {
            if (!ines._instances->at (i).missing ())
            {
                total++;
                if (test_instance (ines._instances->at (i)))
                {
                    correct++;
                }
            }
        }

        if (total == 0)
            return accuracy;

        return ((float)correct / (float)total);
    }

    //RETURN the probability of the label
    float predict_instance (instance & ins)
    {
        //fix missing here
        //
        float max_prob = 0;
        string max_label = "";

        // every attribution
        // P(X|Ci)=P(x1|Ci)P(x2|Ci)...P(xn|Ci)
	cout<<endl<<"----------------------------"<<endl;
	cout <<"tuple:"<<ins._major._numeric<<endl;
        for (int j = 0; j < ins.label_info ()._range.size (); j++)
        {
            string class_label = ins.label_info ()._range.at (j);
            int total_ins = _label_map[class_label];
            float result = 1;
            float percent = 1;

	    cout<<endl<<class_label<<":"<<endl;
            for (int i = 0; i < ins._attrs.size (); i++)
            {
                if (ins._attrs.at (i).type () == ATTR_DISCRETE)
                {
                    string attr = ins._attrs.at (i).discrete ();
                    string cond_prob = attr + "|" + class_label;
                    int count =
                        _training_coef.at (i)._discrete_attr_map[cond_prob];
                    percent = (float) count / (float) total_ins;
                    result = (float)result *(float) percent;
		    cout<<"["<<count<<","<<total_ins<<","<<percent<<","<<result<<"]  "<<endl;
                }
                else
                {
                    float val = ins._attrs.at (i).numeric ();

                    float mean = _training_coef.at (i)._numeric_attr_map["mu|" + class_label];

                    float variance = _training_coef.at (i)._numeric_attr_map["sigma|" + class_label];

                    percent = cont_cond_prob (val, mean, variance);
                    result = result * percent;
		    cout<<"{"<<val<<","<<percent<<","<<result<<"}  "<<endl;

                }
            }

            // P(X|Ci)P(X)
            int ti = _training_set->size();
            float py = (float)total_ins/(float)ti;
	    cout<<"-----"<<endl;
	    cout<<"P(X|Y)="<<result<<" "<<endl;
            result *= py;
	    cout<<"P(Y)="<<py<<" "<<endl;
	    cout<<"P(X|Y)P(Y)="<<result<<endl;

            if (result >= max_prob)
            {
                max_prob = result;
                max_label = class_label;
                // cout << max_prob << "  " << max_label << endl;
            }
        }




	//  to calc p(x_i)
	    float px = 1;
	    float pxi = 1;

            for (int i = 0; i < ins._attrs.size (); i++)
            {
                if (ins._attrs.at (i).type () == ATTR_DISCRETE)
                {
                    string attr = ins._attrs.at (i).discrete ();
		    pxi = (float)_training_coef.at (i)._discrete_attr_map[attr]/(float)_training_set->size();
		    px *= pxi;
                }
                else
                {
                    float val = ins._attrs.at (i).numeric ();
                    float mean_all = _training_coef.at (i)._numeric_attr_map["mu"];
                    float variance_all = _training_coef.at (i)._numeric_attr_map["sigma"];

                    pxi = cont_cond_prob (val, mean_all, variance_all);
		    //cout<<pxi<<endl;
		    px *= pxi;
                }
            }



        ins._label.discrete (max_label);
	cout<<endl<<"P(X|Y)p(Y)="<<max_prob<<endl<<"P(X)="<<px<<endl;
//	float px = count_instance(ins);
        max_prob = (float) max_prob/(float)px; 
	cout<<"LAST="<<max_prob<<endl;
        return max_prob;
    }














    // RETURN the true if the prediction is right.
    bool test_instance (instance & ins)
    {
        string orig_label = ins._label._discrete;
        predict_instance (ins);
        if (ins._label._discrete == orig_label)
            return true;
        else
            return false;
    }

//===============================================================
//  Calculate The Coef. From The Training Set
//===============================================================
// calculate the coef from the learning set.
// Need two time scan.
// MUST operator after calculate_label_num
    void calculate_training_coef ()
    {
        _training_coef.clear ();
        //init the counter to 0.
        for (int i = 0; i < _training_set->_bitmap.size (); i++)
        {
            attr_prob coef (_training_set->_bitmap.at (i),
                            _training_set->_attr_infos);
            _training_coef.push_back (coef);
        }
        //1st time scan
        for (int i = 0; i < _training_set->size (); i++)
        {
            update_coef (_training_set->_instances->at (i));
        }

        //2nd time scan
        //to calculate the sigma*sigma
        for (int i = 0; i < _training_set->size (); i++)
        {
            for (int j = 0;
                    j < _training_set->_instances->at (i)._attrs.size (); j++)
            {
                instance ins = _training_set->_instances->at (i);
                if (ins._attrs.at (j).type () == ATTR_NUMERIC)
                {
                    float numeric = ins._attrs.at (j).numeric ();
                    string label = ins._label.discrete ();
                    string smu = "mu|" + label;
                    float mu = _training_coef.at (j)._numeric_attr_map[smu];
                    float mu_all = _training_coef.at (j)._numeric_attr_map["mu"];

                    float delt =
                        (numeric - mu) * (numeric - mu) /(float) _label_map[label];

                    float delt_all =
                        (numeric - mu_all) * (numeric - mu_all) / (float)_training_set->size();
                    string item = "sigma|" + label;
                    _training_coef.at (j)._numeric_attr_map[item] += delt;
                    _training_coef.at (j)._numeric_attr_map["sigma"] += delt_all;
                }
            }
        }
    }
    // utils for calculate the coef.
    // calculate the new  number of discrete attribute
    // calculate the nean of numeric attribute
    void update_coef (instance ins)
    {

        for (int i = 0; i < ins._attrs.size (); i++)
        {
            if (ins._attrs.at (i).type () == ATTR_DISCRETE)
            {
                string prefix = ins._attrs.at (i).discrete ();
                string item = prefix + "|" + ins._label.discrete ();
                _training_coef.at (i)._discrete_attr_map[item]++;
                _training_coef.at (i)._discrete_attr_map[prefix]++;

            }
            else			//ATTR_NUMERIC
            {
                float numeric = ins._attrs.at (i).numeric ();
                string label = ins._label.discrete ();
                float delt = numeric /(float) _label_map[label];
                float delt_all = numeric / (float)_training_set->size();
                string item = "mu|" + label;
                _training_coef.at (i)._numeric_attr_map[item] += delt;
                _training_coef.at (i)._numeric_attr_map["mu"] += delt_all;
            }
        }
    }

    // Calculate the number of every classify label in the training set.
    // scan the set first order.Why? See my paper.
    // test OK.
    void calculate_label_num ()
    {
        _label_map.clear ();
        attribute_info linfo = _training_set->label_attr_info ();
        for (int i = 0; i < linfo._range.size (); i++)
            _label_map.insert (std::make_pair (linfo._range.at (i), 0));
        for (int i = 0; i < _training_set->_instances->size (); i++)
            _label_map[_training_set->_instances->at (i)._label._discrete]++;
    }

    
    //count P(X)
    float count_instance(instance& ins)
    {
	    int count = 0;
        for(int i=0;i<_training_set->_instances->size();i++)
       {
              if(_training_set->_instances->at(i).same_with(ins))
		      count ++;
       }
	   int total = _training_set->_instances->size();
	   float percent = (float) count/ (float) total;
	   cout<<(float)percent<<endl;
	   //00000000000000000000?
	   return percent;
    }

//============================================================
//Member
//============================================================
    void set_training_set (instances * set)
    {
        _training_set = set;
    }

    void set_classify_set (instances * set)
    {
        _classify_set = set;
    }

    void set_test_set (instances * set)
    {
        _test_set = set;
    }

//============================================================
//print coef.
//============================================================
    void print_coef ()
    {
        cout << "2G: " << _label_map["2G"] << endl;
        cout << "3G: " << _label_map["3G"] << endl;

        for (int i = 0; i < _training_coef.size (); i++)
        {
            cout << "------------arg" << i << "---------------------" << endl;
            if (_training_coef.at (i)._type == ATTR_DISCRETE)
            {
                map < string, int >::iterator iter =
                    _training_coef.at (i)._discrete_attr_map.begin ();
                map < string, int >::iterator iter_end =
                    _training_coef.at (i)._discrete_attr_map.end ();

                while (iter != iter_end)
                {
                    cout << iter->first << " " << iter->second << endl;
                    iter++;
                }
            }
            else
            {
                cout << "mu|2G:" << _training_coef.at (i)._numeric_attr_map["mu|2G"] << endl;
                cout << "mu|3G:" << _training_coef.at (i)._numeric_attr_map["mu|3G"] << endl;
                cout << "sigma|2g:" << _training_coef.at (i)._numeric_attr_map["sigma|2G"] << endl;
                cout << "sigma|3g:" << _training_coef.at (i)._numeric_attr_map["sigma|3G"] << endl;
                cout << "mu:" << _training_coef.at (i)._numeric_attr_map["mu"] << endl;
                cout << "sigma:" << _training_coef.at (i)._numeric_attr_map["sigma"] << endl;
            }
        }

    }

    friend ostream & operator << (ostream & os, const naive_bayes & bayes)
    {
        os << "*************LABEL NUMBER*****************" << endl;
        map < string, int >::const_iterator iter = bayes._label_map.begin ();
        while (iter != bayes._label_map.end ())
        {
            os << iter->first << ": " << iter->second << endl;
            iter++;
        }
        os << "******************************************" << endl;

        copy (bayes._training_coef.begin (), bayes._training_coef.end (),
              ostream_iterator < attr_prob > (os));
        return os;
    }
    //print the result
    void print_classified_set (ostream & os)
    {
        copy (_classified_set.begin (), _classified_set.end (),
              ostream_iterator < major_prob > (os));
    }

//==========================================================
//              member structure
//==========================================================
    map < string, int >_label_map;
    vector < attr_prob > _training_coef;
    vector < major_prob > _classified_set;

    float (*cont_cond_prob) (float, float, float);
    instances *_training_set;
    instances *_test_set;
    instances *_classify_set;
};
#endif
